#pragma once
#include <functional>
#include <algorithm>
#include <list>
#include <cstdint>

/**  
 *  @addtogroup  Events
 * @{
 **/

template <typename TSignature>
class SignalBase;


class SignalRef
{
public:
	SignalRef() : mRefs(1), alive(true) {}

	void retain() {++mRefs;}
	void release() 
	{
		--mRefs; 
		if (mRefs == 0) 
		{
			delete this;
		}
	}

	bool alive;

	unsigned int references() const {return mRefs;}
private:
	unsigned int mRefs;
};

//====== Slot ======


class AbstractSlot
{
public:
	uint32_t refCount;
	bool connected;
	SignalRef* ref;

	AbstractSlot() : refCount(1), connected(true), ref(nullptr) {}
	virtual ~AbstractSlot() {if (ref) ref->release(); ref = nullptr; }

	void retain() { refCount++;}
	void release() { refCount--; if (refCount == 0) delete this; }

	virtual void connect() = 0;
	virtual void disconnect() = 0;
};

template <typename TSignature>
class ConcreteSlot;

// Connection

class Connection
{
public:
	Connection() : _slot(nullptr) {}
	Connection(const Connection& other) : _slot(other._slot) {_slot->retain(); }
	Connection(AbstractSlot* slot) : _slot(slot) {_slot->retain();}

	~Connection() { _slot->release(); }
	const Connection& operator=(const Connection& other)
	{
		if (_slot)
			_slot->release();
		_slot = other._slot;
		_slot->retain();
		return *this;
	}

	void connect() {_slot->connect();}
	void disconnect() {_slot->disconnect();}
	bool isConnected() const
	{
		if (_slot == nullptr)
			return false;
		return _slot->connected;
	}
//protected:
	AbstractSlot* _slot;
};

// ======= SignalBase =========

/**
* @brief   Base signal.
* 			Implements base functions of signal-slot system
*/
template <typename TSignature>
class SignalBase
{
public:
	SignalBase();
	virtual ~SignalBase ();
	typedef std::function < TSignature > function_t;

	/**
	* @brief   Connects function as new slot.
	*
	* @param   handler The function to connect.
	*
	* @return  Slot.
	*/
	Connection connect( function_t handler);

	void disconnectAll(); // all of 'em

	std::list < ConcreteSlot<TSignature>* > _slots;
	SignalRef* mRef;
};
;

template <typename TSignature>
class ConcreteSlot : public AbstractSlot
{
public:
	explicit ConcreteSlot(SignalBase<TSignature>* _host, const std::function<TSignature>& _foo) : 
		host(_host), 
		foo(_foo)
	{
		host->mRef->retain();
		ref = host->mRef;
	}
	virtual ~ConcreteSlot() 
	{
	}
	std::function<TSignature> foo;
	SignalBase<TSignature>* host;
	virtual void connect() override 
	{ 
		if (connected) return; 
		connected = true; 
		host->_slots.push_back(this); retain(); 
	}
	virtual void disconnect() override 
	{ 
		if (!connected) return;
		connected = false;
		host->_slots.remove(this);
		release();
	}
};


//======= SignalBase implementation ====

template <typename TSignature>
SignalBase<TSignature>::~SignalBase()
{
	disconnectAll();mRef->alive = false; 
	mRef->release();
	mRef = nullptr;
}

template <typename TSignature>
SignalBase<TSignature>::SignalBase() : mRef(new SignalRef())
{

}

template < typename TSignature >
Connection SignalBase < TSignature >::connect(std::function < TSignature > handler)
{
	ConcreteSlot<TSignature>* s = new ConcreteSlot<TSignature>(this, handler);
	_slots.push_back(s);

	return Connection(s);
}

template < typename TSignature >
void SignalBase <TSignature>::disconnectAll( )
{
	for ( auto it = this->_slots.begin(); it != this->_slots.end(); ++it)
	{
		auto slot = *it;
		slot->connected = false;
		slot->release();
	}

	_slots.clear();
}

// ===== dirty tricks ====

/**
* @brief   Signal.
* 			
* @tparam      TSignature      Signature of signal method.
*/
template <typename TSignature >
class Signal : public SignalBase < TSignature >
{
};

// 0 parameters specialization
template <>
class Signal < void (void) > : public SignalBase < void (void) > 
{
	typedef SignalBase < void (void) > base_t;
public:
	void operator () (void)    
	{
		for (auto it = this->_slots.begin(); it != this->_slots.end(); ++it )
		{
			(*it)->foo();
		}
	}
};

// 1 parameter specialization
template < typename TArg >
class Signal < void (TArg) > : public SignalBase < void (TArg) > 
{
	typedef SignalBase < void (TArg) > base_t;
public:
	void operator () (TArg arg)    
	{
		for (auto it = this->_slots.begin(); it != this->_slots.end(); ++it )
		{
			(*it)->foo(arg);
		}
	}
};

// 2 parameters specialization
template < typename TArg1, typename TArg2 >
class Signal < void (TArg1, TArg2) > : public SignalBase < void (TArg1, TArg2) > 
{
	typedef SignalBase < void (TArg1, TArg2) > base_t;
public:
	void operator () (TArg1 a1, TArg2 a2)    
	{
		for (auto it = this->_slots.begin(); it != this->_slots.end(); ++it )
		{
			(*it)->foo(a1, a2);
		}
	}
};

// 3 parameters specialization
template < typename TArg1, typename TArg2, typename TArg3 >
class Signal < void (TArg1, TArg2, TArg3) > : public SignalBase < void (TArg1, TArg2, TArg3) > 
{
	typedef SignalBase < void (TArg1, TArg2, TArg3) > base_t;
public:
	void operator () (TArg1 a1, TArg2 a2, TArg3 a3)    
	{
		for (auto it = this->_slots.begin(); it != this->_slots.end(); ++it )
		{
			(*it)->foo(a1, a2, a3);
		}
	}
};
// helpers
template <class TClass>
Connection connect(Signal < void (void) >& signal, TClass* obj, void (TClass::*foo)(void))
{
	return signal.connect(std::bind(foo, obj)); // 2 velorth: use of placeholders::_1 forwards 1st func's arg to be passed at function obj caller. void - is not a valid arg type!!! you should burn in fire.
}

template <class TClass, typename TArg1>
Connection connect(Signal < void (TArg1) >& signal, TClass* obj, void (TClass::*foo)(TArg1))
{
	return signal.connect(std::bind(foo, obj, std::placeholders::_1));
}

template <class TClass, typename TArg1, typename TArg2>
Connection connect(Signal < void (TArg1, TArg2) >& signal, TClass* obj, void (TClass::*foo)(TArg1, TArg2))
{
	return signal.connect(std::bind(foo, obj, std::placeholders::_1, std::placeholders::_2));
}

template <class TClass, typename TArg1, typename TArg2, typename TArg3>
Connection connect(Signal < void (TArg1, TArg2, TArg3) >& signal, TClass* obj, void (TClass::*foo)(TArg1, TArg2, TArg3))
{
	return signal.connect(std::bind(foo, obj, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
}

/** 
* @}
**/
